/*
 * $Id: $
 */
package org.rcgwt.client.ui.grid.internal;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.rcgwt.client.ui.grid.internal.model.ICellModel;
import org.rcgwt.client.ui.grid.internal.model.IColumnModel;
import org.rcgwt.client.ui.grid.internal.model.IElementModel;
import org.rcgwt.client.ui.grid.internal.model.IGridModel;
import org.rcgwt.client.ui.grid.internal.model.IGroupModel;
import org.rcgwt.client.ui.grid.internal.model.IGroupTitleModel;
import org.rcgwt.client.ui.grid.internal.model.IHeaderModel;
import org.rcgwt.client.ui.grid.internal.model.IRowModel;
import org.rcgwt.client.ui.grid.internal.model.ITitleCellModel;
import org.rcgwt.client.ui.grid.internal.model.ITitleRowModel;
import org.rcgwt.client.ui.grid.internal.model.IViewModel;
import org.rcgwt.client.ui.grid.internal.model.IViewModelElements;
import org.rcgwt.client.ui.grid.renderer.IGridRenderContext;
import org.rcgwt.client.ui.grid.renderer.IRowRenderer;

import com.google.gwt.dom.client.Element;

/**
 * 
 * @author Olivier Oeuillot (latest modification by $Author: $)
 * @version $Revision: $ $Date: $
 */
public class ViewModelElements extends AbstractViewModel implements
        IViewModelElements {

    private static final String MODEL_INDEX_ATTRIBUTE = "_mdlIndex";

    private static final String TYPE_ATTRIBUTE = "_mdlType";

    private Element tableElement;

    private Element scrollableTableContainer;

    private Element viewElement;

    private final Map<IHeaderModel, Element> headerModelElements = new HashMap<IHeaderModel, Element>();

    private final Map<IGroupModel, Element> groupModelElements = new HashMap<IGroupModel, Element>();

    private final Map<IGroupTitleModel, Element> groupTitleElements = new HashMap<IGroupTitleModel, Element>();

    private final Map<IColumnModel, Element> columnModelElements = new HashMap<IColumnModel, Element>();

    private final Map<ITitleCellModel, Element> titleCellModelElements = new HashMap<ITitleCellModel, Element>();

    private final Map<ITitleRowModel, Element> titleRowModelElements = new HashMap<ITitleRowModel, Element>();

    private final Map<IGroupModel, Map<Integer, Element>> rowModelIndexElements = new HashMap<IGroupModel, Map<Integer, Element>>();

    private final Set<IRowModel> visibleRowModels = new HashSet<IRowModel>();

    public ViewModelElements(int modelIndex, IGridModel gridModel,
            int renderHints) {
        super(modelIndex, gridModel, renderHints);

        setViewModelElements(this);
    }

    @Override
    public void clearCellElements(IRowModel rowModel, Element rowElement) {
        // Nothing
    }

    @Override
    public void clearRowElements(IGroupModel groupModel, Element groupElement) {
        Map<Integer, Element> rowModelElementsByGroup = rowModelIndexElements
                .get(groupModel);

        if (rowModelElementsByGroup != null) {
            rowModelElementsByGroup.clear();
        }
    }

    @Override
    public Element getCellElement(IGridRenderContext gridRenderContext,
            ICellModel cellModel) {

        IRowModel rowModel = cellModel.getRowModel();

        Element rowElement = getRowElement(rowModel);
        if (rowElement == null) {
            return null;
        }

        int columnRenderedIndexes[] = gridRenderContext
                .getColumnRenderedIndexes();

        int cellRenderedIndex = columnRenderedIndexes[cellModel.getModelIndex()];

        IRowRenderer rowRenderer = getGridModel().getGridWidget().getTheme()
                .getRowRenderer(rowModel);

        return rowRenderer.getCellElement(rowElement, cellModel,
                cellRenderedIndex);
    }

    @SuppressWarnings("boxing")
    @Override
    public Element getRowElement(IRowModel rowModel) {

        return rowModelIndexElements.get(rowModel.getGroupModel()).get(
                rowModel.getModelIndex());
    }

    @Override
    public Element getScrollableTableContainer() {
        return scrollableTableContainer;
    }

    @Override
    public Element getTableElement() {
        return tableElement;
    }

    @Override
    public Element getViewElement() {
        return viewElement;
    }

    @Override
    public void setCellElement(ICellModel cell, Element cellElement,
            int renderedIndex) {
    }

    @SuppressWarnings("boxing")
    @Override
    public void setRowElement(IRowModel rowModel, Element rowElement,
            int renderedIndex, boolean visible) {

        IGroupModel groupModel = rowModel.getGroupModel();

        Map<Integer, Element> rowModelElementsByGroup = rowModelIndexElements
                .get(groupModel);

        if (rowModelElementsByGroup == null) {
            rowModelElementsByGroup = new HashMap<Integer, Element>();
            rowModelIndexElements.put(groupModel, rowModelElementsByGroup);
        }

        rowModelElementsByGroup.put(rowModel.getModelIndex(), rowElement);

        setElementProperty(rowModel, rowElement);

        if (visible) {
            visibleRowModels.add(rowModel);

        } else {
            visibleRowModels.remove(rowModel);
        }
    }

    protected void setElementProperty(IElementModel elementModel,
            Element element) {
        element.setPropertyInt(MODEL_INDEX_ATTRIBUTE, elementModel
                .getModelIndex());

        element
                .setPropertyInt(TYPE_ATTRIBUTE, elementModel.getType()
                        .ordinal());
    }

    @Override
    public void setScrollableTableContainer(Element scrollableTableContainer) {
        this.scrollableTableContainer = scrollableTableContainer;
    }

    @Override
    public void setTableElement(Element tableElement) {
        this.tableElement = tableElement;
    }

    @Override
    public void setViewElement(Element element) {
        this.viewElement = element;

        setElementProperty(this, element);
    }

    @Override
    public Element getHeaderElement(IHeaderModel header) {
        return headerModelElements.get(header);
    }

    @Override
    public void setHeaderElement(IHeaderModel headerModel, Element headerElement) {
        headerModelElements.put(headerModel, headerElement);

        setElementProperty(headerModel, headerElement);
    }

    @Override
    public void clearGroupAndColumnsElements(IViewModel viewModel,
            Element viewElement) {
        groupModelElements.clear();
        groupTitleElements.clear();

        columnModelElements.clear();
    }

    @Override
    public void setColumnElement(IColumnModel columnModel,
            Element columnElement, int renderedIndex) {
        columnModelElements.put(columnModel, columnElement);
    }

    @Override
    public void setGroupElement(IGroupModel groupModel, Element groupElement,
            int renderedIndex) {
        groupModelElements.put(groupModel, groupElement);

        setElementProperty(groupModel, groupElement);
    }

    @Override
    public void setGroupTitleElement(IGroupTitleModel groupTitleModel,
            Element groupTitleElement) {
        groupTitleElements.put(groupTitleModel, groupTitleElement);

        setElementProperty(groupTitleModel, groupTitleElement);
    }

    @Override
    public Element getColumnElement(IColumnModel columnModel) {
        return columnModelElements.get(columnModel);
    }

    @Override
    public Element getGroupElement(IGroupModel groupModel) {
        return groupModelElements.get(groupModel);
    }

    @Override
    public Element getGroupTitleElement(IGroupTitleModel groupTitleModel) {
        return groupTitleElements.get(groupTitleModel);
    }

    @Override
    public Element getTitleCellElement(ITitleCellModel titleCellModel) {
        return titleCellModelElements.get(titleCellModel);
    }

    @Override
    public Element getTitleRowElement(ITitleRowModel titleRowModel) {
        return titleRowModelElements.get(titleRowModel);
    }

    @Override
    public void setTitleCellElement(ITitleCellModel titleCellModel,
            Element titleCellElement, int renderedIndex) {
        titleCellModelElements.put(titleCellModel, titleCellElement);

        setElementProperty(titleCellModel, titleCellElement);
    }

    @Override
    public void setTitleRowElement(ITitleRowModel titleRowModel,
            Element titleRowElement, int renderedIndex) {
        titleRowModelElements.put(titleRowModel, titleRowElement);

        setElementProperty(titleRowModel, titleRowElement);
    }

    @Override
    public IRowModel[] listVisibleRowModels(IViewModel viewModel) {

        return visibleRowModels.toArray(new IRowModel[visibleRowModels.size()]);
    }

    @Override
    public IElementModel findElementModel(IViewModel viewModel, Element element) {

        for (; element != null; element = element.getParentElement()) {
            String typeString = element.getPropertyString(TYPE_ATTRIBUTE);
            if (typeString == null) {
                continue;
            }

            int typeInt = Integer.parseInt(typeString);

            Type type = Type.values()[typeInt];
            switch (type) {
            case CELL:
                return findCellModel(viewModel, element);

            case ROW:
                return findRowModel(viewModel, element);

            case GROUP:
                return findGroupModel(viewModel, element);

            case GROUP_TITLE:
                return findGroupTitleModel(viewModel, element);

            case VIEW:
                return findViewModel(viewModel, element);

            case GRID:
                return viewModel.getGridModel();

            case TITLE_CELL:
                return findTitleCellModel(viewModel, element);

            case TITLE_ROW:
                return findTitleRowModel(viewModel, element);

            case HEADER:
                return findHeaderModel(viewModel, element);

            default:
                break;
            }
        }
        return null;
    }

    private IViewModel findViewModel(IViewModel viewModel, Element element) {
        int viewModelIndex = element.getPropertyInt(MODEL_INDEX_ATTRIBUTE);

        IViewModel viewModels[] = viewModel.getGridModel().listViewModels();

        assert viewModelIndex >= 0 && viewModelIndex < viewModels.length : "Invalid view index 0<="
                + viewModelIndex + "<" + viewModels.length;

        return viewModels[viewModelIndex];
    }

    private IHeaderModel findHeaderModel(IViewModel viewModel, Element element) {
        int headerModelIndex = element.getPropertyInt(MODEL_INDEX_ATTRIBUTE);

        IViewModel viewModelTarget = (IViewModel) findElementModel(viewModel,
                element.getParentElement());
        if (viewModelTarget == null) {
            return null;
        }

        IHeaderModel headerModels[] = viewModel.listHeaderModels();

        assert headerModelIndex >= 0 && headerModelIndex < headerModels.length : "Invalid header index 0<="
                + headerModelIndex + "<" + headerModels.length;

        return headerModels[headerModelIndex];
    }

    private ITitleRowModel findTitleRowModel(IViewModel viewModel,
            Element element) {
        int rowModelIndex = element.getPropertyInt(MODEL_INDEX_ATTRIBUTE);

        IHeaderModel headerModel = (IHeaderModel) findElementModel(viewModel,
                element.getParentElement());
        if (headerModel == null) {
            return null;
        }

        ITitleRowModel titleRowModels[] = headerModel.listTitleRowModels();

        assert rowModelIndex >= 0 && rowModelIndex < titleRowModels.length : "Invalid row index 0<="
                + rowModelIndex + "<" + titleRowModels.length;

        return titleRowModels[rowModelIndex];
    }

    private ITitleCellModel findTitleCellModel(IViewModel viewModel,
            Element element) {
        int cellModelIndex = element.getPropertyInt(MODEL_INDEX_ATTRIBUTE);

        ITitleRowModel titleRowModel = (ITitleRowModel) findElementModel(
                viewModel, element.getParentElement());
        if (titleRowModel == null) {
            return null;
        }

        ITitleCellModel titleCellModels[] = titleRowModel.listTitleCellModels();

        assert cellModelIndex >= 0 && cellModelIndex < titleCellModels.length : "Invalid column index 0<="
                + cellModelIndex + "<" + titleCellModels.length;

        return titleCellModels[cellModelIndex];
    }

    private ICellModel findCellModel(IViewModel viewModel, Element element) {
        IGridModel gridModel = viewModel.getGridModel();

        int cellModelIndex = element.getPropertyInt(MODEL_INDEX_ATTRIBUTE);

        IColumnModel columnModels[] = gridModel.listColumnModels();

        assert cellModelIndex >= 0 && cellModelIndex < columnModels.length : "Invalid column index 0<="
                + cellModelIndex + "<" + columnModels.length;

        IColumnModel columnModel = columnModels[cellModelIndex];

        IRowModel rowModel = (IRowModel) findElementModel(viewModel, element
                .getParentElement());
        if (rowModel == null) {
            return null;
        }

        return rowModel.getCellModel(columnModel);
    }

    private IRowModel findRowModel(IViewModel viewModel, Element element) {
        IGridModel gridModel = viewModel.getGridModel();

        int rowModelIndex = element.getPropertyInt(MODEL_INDEX_ATTRIBUTE);

        return gridModel.searchRowModel(rowModelIndex);
    }

    private IGroupModel findGroupModel(IViewModel viewModel, Element element) {
        IGridModel gridModel = viewModel.getGridModel();

        int groupModelIndex = element.getPropertyInt(MODEL_INDEX_ATTRIBUTE);

        IGroupModel groupModels[] = gridModel.listGroupModels();

        assert groupModelIndex >= 0 && groupModelIndex < groupModels.length : "Invalid group index 0<="
                + groupModelIndex + "<" + groupModels.length;

        return groupModels[groupModelIndex];
    }

    private IGroupTitleModel findGroupTitleModel(IViewModel viewModel,
            Element element) {
        IGroupModel groupModel = (IGroupModel) findElementModel(viewModel,
                element.getParentElement());
        if (groupModel == null) {
            return null;
        }

        int groupTitleModelIndex = element
                .getPropertyInt(MODEL_INDEX_ATTRIBUTE);

        IGroupTitleModel groupTitleModels[] = groupModel.listGroupTitleModels();

        assert groupTitleModelIndex >= 0
                && groupTitleModelIndex < groupTitleModels.length : "Invalid group title index 0<="
                + groupTitleModelIndex + "<" + groupTitleModels.length;

        return groupTitleModels[groupTitleModelIndex];
    }
}
